import("cpp");

CArray = new cpp.Plugin( "CArray" );
with( CArray )
{
    Include("<cstdio>");
    Include("../plugin/CArray.h");

    Extra = <%
        extern Persistent<Object> ClassDict;
    %>

    Init = <%
        // Expose for other plugins to use
        Handle<Object> Handle_CArray_Int = obj->Get( String::New( "Integer" ) )->ToObject();
        Handle<Object> Handle_CArray_Dbl = obj->Get( String::New( "Double" ) )->ToObject();
        Handle<Object> Handle_CArray_Str = obj->Get( String::New( "String" ) )->ToObject();
        ClassDict->Set( String::New( "CArray_Integer" ), Handle_CArray_Int );
        ClassDict->Set( String::New( "CArray_Double" ), Handle_CArray_Dbl );
        ClassDict->Set( String::New( "CArray_String" ), Handle_CArray_Str );
    %>

    Classes.String = new Class( "String", 2 )
    with( Classes.String )
    {
        // Constructor - create array
        Constructor = <%
            // Check for valid array size
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected array size" ) );
            else if ( !args[0]->IsInt32() && !args[0]->IsString() && !args[0]->IsArray() )
                return ThrowException( String::New( "Array size must be integer or string" ) );
            else if ( args[0]->Int32Value() < 1 && !args[0]->IsString() && !args[0]->IsArray() )
                return ThrowException( String::New( "Invalid array size" ) );
            else if ( args[0]->IsString() && args[0]->ToString()->Length() == 0 )
                return ThrowException( String::New( "Reference string is empty" ) );

            // Create array from pointer
            carray_string *new_array = new carray_string;
            bool preset = false;
            if ( args.Length() > 1 )
            {
                if ( args[1]->IsExternal() )
                {
                    preset = true;
                    new_array->size = args[0]->Int32Value();
                    new_array->array = (char *)Handle<External>::Cast( args[1] )->Value();
                    new_array->is_slice = 1;
                }
            }

            // Create new array
            if ( !preset )
            {
                if ( args[0]->IsArray() )
                {
                    Handle<Array> Arr = Handle<Array>::Cast( args[0] );
                    if ( Arr->Length() == 0 )
                        return ThrowException( String::New( "Invalid length" ) );
                    new_array->size = Arr->Length();
                    new_array->array = new char[ new_array->size ];
                    new_array->is_slice = 0;
                    for ( int i=0; i<new_array->size; i++ )
                    {
                        Handle<Value> Val = Arr->Get( Int32::New( i ) );
                        if ( Val->IsString() )
                        {
                            if ( Val->ToString()->Utf8Length() > 0 )
                                Val->ToString()->WriteUtf8( &new_array->array[i], 1 );
                            else
                                new_array->array[i] = 0;
                        }
                        else
                            new_array->array[i] = Val->Int32Value();
                    }
                }
                else if ( args[0]->IsString() )
                {
                    // Copy from string
                    Handle<String> Str = args[0]->ToString();
                    new_array->size = Str->Utf8Length()+1;
                    new_array->array = new char[ new_array->size ];
                    Str->WriteUtf8( new_array->array );
                    new_array->array[ new_array->size ] = '\0';
                    new_array->is_slice = 0;
                }
                else
                {
                    // Blank array
                    new_array->size = args[0]->Int32Value();
                    if (new_array->size > 0)
                        new_array->array = new char[ new_array->size ];
                    else
                    {
                        new_array->array = 0;
                        new_array->size = -1;
                    }
                    new_array->is_slice = 0;
                }
            }

            // Set internal fields
            args.This()->SetInternalField( 0, Integer::New( TYPE_STRING ) );
            args.This()->SetInternalField( 1, External::New( new_array ) );
        %>

        // Destructor - delete array
        Destructor = <%
            carray_string *array = (carray_string *)Local<External>::Cast( obj->ToObject()->GetInternalField( 1 ) )->Value();

            if ( !array->is_slice )
                delete array->array;

            delete array;
        %>

        // Indexed setter - write array
        IndexedSetter = <%
            // Check bounds
            carray_string *array = (carray_string *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );
            
            // Set array field
            array->array[index] = (unsigned char)value->Int32Value();

        %>

        // Indexed getter - read array
        IndexedGetter = <%
            // Check bounds
            carray_string *array = (carray_string *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );

            // Get array field
            return Int32::New( (int)((unsigned char)array->array[index]) );
        %>

        Getters.length = <%
            carray_string *array = (carray_string *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            return Integer::New( array->size );
        %>

        Methods.slice = <%
            carray_string *array = (carray_string *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            int start;
            int end;

            // Check arguments
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected: bounds" ) );
            else if ( args.Length() < 2 )
            {
                int arg0 = args[0]->Int32Value();
                if ( arg0 < 0 )
                {
                    start = array->size - arg0;
                    end = array->size;
                }
                else if ( arg0 > 0 )
                {
                    start = 0;
                    end = arg0 + 1;
                }
                else
                    return ThrowException( String::New( "Invalid bounds" ) );
            }
            else
            {
                start = args[0]->Int32Value();
                end = args[1]->Int32Value();
            }

            // Check bounds
            if ( ( start < 0 || start >= array->size ) ||
                 ( end < 1 || end > array->size ) ||
                 ( end <= start ) )
                return ThrowException( String::New( "Invalid bounds" ) );           

            // Make new (little array)
            Handle<Value> One = Integer::New( 1 );
            Local<Function> CArray = Local<Function>::Cast( ClassDict->Get( String::New( "CArray_String" ) ) );
            Handle<Object> NewArray = CArray->NewInstance( 1, &One );

            // Warp array 
            carray_string *new_array = (carray_string *)Local<External>::Cast( NewArray->GetInternalField( 1 ) )->Value();
            delete new_array->array;
            new_array->size = end - start;
            new_array->array = array->array + start;
            new_array->is_slice = 1;

            return NewArray;
        %>

        Methods.toString = <%
            carray_string *array = (carray_string *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            return String::New( array->array );
        %>

        Methods.toArray = <%
            carray_string *array = (carray_string *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            
            Handle<Array> NewArray = Array::New();
            for (int i=0; i<array->size; i++)
                NewArray->Set( Int32::New( i ), Int32::New( (int)( (unsigned char)array->array[i] ) ) );

            return NewArray;            
        %>
    }

    Classes.Integer = new Class( "Integer", 2 )
    with( Classes.Integer )
    {
        // Constructor - create array
        Constructor = <%
            // Check for valid array size
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected array size" ) );
            else if ( !args[0]->IsInt32() && !args[0]->IsArray() )
                return ThrowException( String::New( "Array size must be integer" ) );
            else if ( args[0]->Int32Value() < 1 && !args[0]->IsArray() )
                return ThrowException( String::New( "Invalid array size" ) );

            // Create array from pointer
            carray_int *new_array = new carray_int;
            bool preset = false;
            if ( args.Length() > 1 )
            {
                if ( args[1]->IsExternal() )
                {
                    preset = true;
                    new_array->size = args[0]->Int32Value();
                    new_array->array = (int *)Handle<External>::Cast( args[1] )->Value();
                    new_array->is_slice = 1;
                }
            }

            // Create new array
            if ( !preset )
            {
                if ( args[0]->IsArray() )
                {
                    Handle<Array> Arr = Handle<Array>::Cast( args[0] );
                    if ( Arr->Length() == 0 )
                        return ThrowException( String::New( "Invalid length" ) );
                    new_array->size = Arr->Length();
                    new_array->array = new int[ new_array->size ];
                    new_array->is_slice = 0;
                    for ( int i=0; i<new_array->size; i++ )
                    {
                        Handle<Value> Val = Arr->Get( Int32::New( i ) );
                        if ( Val->IsString() )
                        {
                            // Copy first character of string
                            if ( Val->ToString()->Utf8Length() > 0 )
                            {
                                String::Utf8Value Str( Val->ToString() );
                                new_array->array[i] = (unsigned char)((*Str)[0]);
                            }
                            else
                                new_array->array[i] = 0;
                        }
                        else
                            // Copy numerical value
                            new_array->array[i] = Val->Int32Value();
                    }
                }
                else
                {
                    new_array->size = args[0]->Int32Value();
                    if (new_array->size > 0)
                        new_array->array = new int[ new_array->size ];
                    else
                    {
                        new_array->array = 0;
                        new_array->size = -1;
                    }
                    new_array->is_slice = 0;
                }
            }

            // Set internal fields
            args.This()->SetInternalField( 0, Integer::New( TYPE_INTEGER ) );
            args.This()->SetInternalField( 1, External::New( new_array ) );
        %>

        // Destructor - delete array
        Destructor = <%
            carray_int *array = (carray_int *)Local<External>::Cast( obj->ToObject()->GetInternalField( 1 ) )->Value();

            if ( !array->is_slice )
                delete array->array;

            delete array;
        %>

        // Indexed setter - write array
        IndexedSetter = <%
            // Check bounds
            carray_int *array = (carray_int *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );
            
            // Set array field
            array->array[index] = value->Int32Value();

        %>

        // Indexed getter - read array
        IndexedGetter = <%
            // Check bounds
            carray_int *array = (carray_int *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );

            // Get array field
            return Integer::New( array->array[index] );
        %>

        Getters.length = <%
            carray_int *array = (carray_int *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            return Integer::New( array->size );
        %>

        Methods.slice = <%
            carray_int *array = (carray_int *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            int start;
            int end;

            // Check arguments
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected: bounds" ) );
            else if ( args.Length() < 2 )
            {
                int arg0 = args[0]->Int32Value();
                if ( arg0 < 0 )
                {
                    start = array->size - arg0;
                    end = array->size;
                }
                else if ( arg0 > 0 )
                {
                    start = 0;
                    end = arg0 + 1;
                }
                else
                    return ThrowException( String::New( "Invalid bounds" ) );
            }
            else
            {
                start = args[0]->Int32Value();
                end = args[1]->Int32Value();
            }

            // Check bounds
            if ( ( start < 0 || start >= array->size ) ||
                 ( end < 1 || end > array->size ) ||
                 ( end <= start ) )
                return ThrowException( String::New( "Invalid bounds" ) );           

            // Make new (little array)
            Handle<Value> One = Integer::New( 1 );
            Local<Function> CArray = Local<Function>::Cast( ClassDict->Get( String::New( "CArray_Integer" ) ) );
            Handle<Object> NewArray = CArray->NewInstance( 1, &One );

            // Warp array 
            carray_int *new_array = (carray_int *)Local<External>::Cast( NewArray->GetInternalField( 1 ) )->Value();
            delete new_array->array;
            new_array->size = end - start;
            new_array->array = array->array + start;
            new_array->is_slice = 1;

            return NewArray;
        %>

        Methods.toArray = <%
            carray_int *array = (carray_int *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            
            Handle<Array> NewArray = Array::New();
            for (int i=0; i<array->size; i++)
                NewArray->Set( Int32::New( i ), Int32::New( array->array[i] ) );

            return NewArray;            
        %>
    }

    Classes.Double = new Class( "Double", 2 )
    with( Classes.Double )
    {
        // Constructor - create array
        Constructor = <%
            // Check for valid array size
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected array size" ) );
            else if ( !args[0]->IsInt32() && !args[0]->IsArray() )
                return ThrowException( String::New( "Array size must be integer" ) );
            else if ( args[0]->Int32Value() < 1 && !args[0]->IsArray() )
                return ThrowException( String::New( "Invalid array size" ) );

            // Create array from pointer
            carray_double *new_array = new carray_double;
            bool preset = false;
            if ( args.Length() > 1 )
            {
                if ( args[1]->IsExternal() )
                {
                    preset = true;
                    new_array->array = (double *)Handle<External>::Cast( args[1] )->Value();
                    new_array->is_slice = 1;
                }
            }

            // Create new array
            if ( !preset )
            {
                if ( args[0]->IsArray() )
                {
                    Handle<Array> Arr = Handle<Array>::Cast( args[0] );
                    if ( Arr->Length() == 0 )
                        return ThrowException( String::New( "Invalid length" ) );
                    new_array->size = Arr->Length();
                    new_array->array = new double[ new_array->size ];
                    new_array->is_slice = 0;
                    for ( int i=0; i<new_array->size; i++ )
                    {
                        Handle<Value> Val = Arr->Get( Int32::New( i ) );
                        if ( Val->IsString() )
                        {
                            // Copy first character of string
                            if ( Val->ToString()->Utf8Length() > 0 )
                            {
                                String::Utf8Value Str( Val->ToString() );
                                new_array->array[i] = (unsigned char)((*Str)[0]);
                            }
                            else
                                new_array->array[i] = 0;
                        }
                        else
                            // Copy numerical value
                            new_array->array[i] = Val->NumberValue();
                    }
                }
                else
                {
                    new_array->size = args[0]->IntegerValue();
                    if (new_array->size > 0)
                        new_array->array = new double[ new_array->size ];
                    else
                    {
                        new_array->array = 0;
                        new_array->size = -1;
                    }
                    new_array->is_slice = 0;
                }
            }

            // Set internal fields
            args.This()->SetInternalField( 0, Int32::New( TYPE_DOUBLE ) );
            args.This()->SetInternalField( 1, External::New( new_array ) );
        %>

        // Destructor - delete array
        Destructor = <%
            carray_double *array = (carray_double *)Local<External>::Cast( obj->ToObject()->GetInternalField( 1 ) )->Value();
            if ( !array->is_slice )
                delete array->array;
            delete array;
        %>

        // Indexed setter - write array
        IndexedSetter = <%
            // Check bounds
            carray_double *array = (carray_double *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );
            
            // Set array field
            array->array[index] = value->NumberValue();
        %>

        // Indexed getter - read array
        IndexedGetter = <%
            // Check bounds
            carray_double *array = (carray_double *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            if ( ( index < 0 ) || ( index >= array->size ) )
                return ThrowException( String::New( "Index out of bounds" ) );

            // Get array field
            return Number::New( array->array[index] );
        %>

        Getters.length = <%
            carray_double *array = (carray_double *)Local<External>::Cast( info.This()->GetInternalField( 1 ) )->Value();
            return Integer::New( array->size );
        %>

        Methods.slice = <%
            carray_double *array = (carray_double *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            int start;
            int end;

            // Check arguments
            if ( args.Length() < 1 )
                return ThrowException( String::New( "Expected: bounds" ) );
            else if ( args.Length() < 2 )
            {
                int arg0 = args[0]->Int32Value();
                if ( arg0 < 0 )
                {
                    start = array->size - arg0;
                    end = array->size;
                }
                else if ( arg0 > 0 )
                {
                    start = 0;
                    end = arg0 + 1;
                }
                else
                    return ThrowException( String::New( "Invalid bounds" ) );
            }
            else
            {
                start = args[0]->Int32Value();
                end = args[1]->Int32Value();
            }

            // Check bounds
            if ( ( start < 0 || start >= array->size ) ||
                 ( end < 1 || end > array->size ) ||
                 ( end <= start ) )
                return ThrowException( String::New( "Invalid bounds" ) );           

            // Make new (little array)
            Handle<Value> One = Integer::New( 1 );
            Local<Function> CArray = Local<Function>::Cast( ClassDict->Get( String::New( "CArray_Double" ) ) );
            Handle<Object> NewArray = CArray->NewInstance( 1, &One );

            // Warp array 
            carray_double *new_array = (carray_double *)Local<External>::Cast( NewArray->GetInternalField( 1 ) )->Value();
            delete new_array->array;
            new_array->size = end - start;
            new_array->array = array->array + start;
            new_array->is_slice = 1;

            return NewArray;
        %>

        Methods.toArray = <%
            carray_double *array = (carray_double *)Local<External>::Cast( args.This()->GetInternalField( 1 ) )->Value();
            
            Handle<Array> NewArray = Array::New();
            for (int i=0; i<array->size; i++)
                NewArray->Set( Int32::New( i ), Number::New( array->array[i] ) );

            return NewArray;            
        %>
    }
    
    Export();
}
